﻿using System;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Ooaavee.TestTools;

namespace UnitTests.Ooaavee.TestTools
{
    [TestClass]
    public class ThrowsExceptionAttributeTest
    {

        #region VerifyExceptionType

        /// <summary>
        /// Tests VerifyExceptionType method.
        /// </summary>
        [TestMethod]
        public void VerifyExceptionTypeTest1()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException));
            Exception exception = new MyDummyException("Grrr!!! Go away!");

            try
            {
                Verify(target, exception);
                Assert.Fail("Verify failed.");
            }
            catch (AssertFailedException ex)
            {
                Assert.AreEqual("Test method UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest.VerifyExceptionTypeTest1 threw exception UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyDummyException, but exception UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyExceptedException was expected. Exception message: UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyDummyException: Grrr!!! Go away!", ex.Message);
            }
        }

        /// <summary>
        /// Tests VerifyExceptionType method.
        /// </summary>
        [TestMethod]
        public void VerifyExceptionTypeTest2()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyDummyException));
            Exception exception = new MyDummyException("Grrr!!! Go away!");

            Verify(target, exception);
        }

        /// <summary>
        /// Tests VerifyExceptionType method.
        /// </summary>
        [TestMethod]
        [ThrowsException(typeof(MyExceptedException))]
        public void VerifyExceptionTypeTest3()
        {
            throw new MyExceptedException();
        }

        #endregion

        #region VerifyExceptionDerivedType

        /// <summary>
        /// Tests VerifyExceptionDerivedType method.
        /// </summary>
        [TestMethod]
        public void VerifyExceptionDerivedTypeTest1()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { AllowDerivedType = true };
            Exception exception = new MyDummyException("Grrr!!! Go away!");

            try
            {
                Verify(target, exception);
                Assert.Fail("Verify failed.");
            }
            catch (AssertFailedException ex)
            {
                Assert.AreEqual("Test method UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest.VerifyExceptionDerivedTypeTest1 threw exception UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyDummyException, but exception UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyExceptedException was expected. Exception message: UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyDummyException: Grrr!!! Go away!", ex.Message);
            }
        }

        /// <summary>
        /// Tests VerifyExceptionDerivedType method.
        /// </summary>
        [TestMethod]
        public void voidVerifyExceptionDerivedTypeTest2()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyDummyException)) { AllowDerivedType = true };
            Exception exception = new MyExtendedDummyException("Grrr!!! Go away!");

            Verify(target, exception);
        }

        /// <summary>
        /// Tests VerifyExceptionDerivedType method.
        /// </summary>
        [TestMethod]
        [ThrowsException(typeof(MyExceptedException), AllowDerivedType = true)]
        public void VerifyExceptionDerivedTypeTest3()
        {
            throw new MyExtendedException();
        }

        #endregion

        #region VerifyInnerExceptionType

        /// <summary>
        /// Tests VerifyInnerExceptionType method.
        /// </summary>
        [TestMethod]
        public void VerifyInnerExceptionTypeTest1()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { ExpectedInnerException = typeof(MyDummyException) };
            Exception exception = new MyExceptedException("Warning!!!", new InvalidOperationException("Grrr!!! Go away!"));

            try
            {
                Verify(target, exception);
                Assert.Fail("Verify failed.");
            }
            catch (AssertFailedException ex)
            {
                Assert.AreEqual("Test method UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest.VerifyInnerExceptionTypeTest1 threw exception UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyExceptedException with inner exception System.InvalidOperationException, but inner exception UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyDummyException was expected. Exception message: UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyExceptedException: Warning!!!. Inner exception message: System.InvalidOperationException: Grrr!!! Go away!", ex.Message);
            }
        }

        /// <summary>
        /// Tests VerifyInnerExceptionType method.
        /// </summary>
        [TestMethod]
        public void VerifyInnerExceptionTypeTest2()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { ExpectedInnerException = typeof(MyDummyException) };
            Exception exception = new MyExceptedException("Warning!!!", new MyDummyException("Grrr!!! Go away!"));

            Verify(target, exception);
        }

        /// <summary>
        /// Tests VerifyInnerExceptionType method.
        /// </summary>
        [TestMethod]
        [ThrowsException(typeof(MyExceptedException), ExpectedInnerException = typeof(MyDummyException))]
        public void VerifyInnerExceptionTypeTest3()
        {
            throw new MyExceptedException(null, new MyDummyException(null));
        }

        /// <summary>
        /// Tests VerifyInnerExceptionType method.
        /// </summary>
        [TestMethod]
        public void VerifyInnerExceptionTypeTest4()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { ExpectedInnerException = typeof(MyDummyException) };
            Exception exception = new MyExceptedException("Warning!!!");

            try
            {
                Verify(target, exception);
                Assert.Fail("Verify failed.");
            }
            catch (AssertFailedException ex)
            {
                Assert.AreEqual("Test method UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest.VerifyInnerExceptionTypeTest4 threw exception UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyExceptedException without inner exception, but inner exception UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyDummyException was expected. Exception message: UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyExceptedException: Warning!!!.", ex.Message);
            }
        }

        #endregion

        #region VerifyInnerExceptionDerivedType

        /// <summary>
        /// Tests VerifyInnerExceptionDerivedType method.
        /// </summary>
        [TestMethod]
        public void VerifyInnerExceptionDerivedTypeTest1()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { ExpectedInnerException = typeof(MyDummyException), AllowInnerExceptionDerivedType = true };
            Exception exception = new MyExceptedException("Warning!!!", new InvalidOperationException("Grrr!!! Go away!"));

            try
            {
                Verify(target, exception);
                Assert.Fail("Verify failed.");
            }
            catch (AssertFailedException ex)
            {
                Assert.AreEqual("Test method UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest.VerifyInnerExceptionDerivedTypeTest1 threw exception UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyExceptedException with inner exception System.InvalidOperationException, but inner exception UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyDummyException was expected. Exception message: UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyExceptedException: Warning!!!. Inner exception message: System.InvalidOperationException: Grrr!!! Go away!", ex.Message);
            }
        }

        /// <summary>
        /// Tests VerifyInnerExceptionDerivedType method.
        /// </summary>
        [TestMethod]
        public void VerifyInnerExceptionDerivedTypeTest2()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { ExpectedInnerException = typeof(MyDummyException), AllowInnerExceptionDerivedType = true };
            Exception exception = new MyExceptedException("Warning!!!", new MyExtendedDummyException("Grrr!!! Go away!"));

            Verify(target, exception);
        }

        /// <summary>
        /// Tests VerifyInnerExceptionDerivedType method.
        /// </summary>
        [TestMethod]
        [ThrowsException(typeof(MyExceptedException), ExpectedInnerException = typeof(MyDummyException), AllowInnerExceptionDerivedType = true)]
        public void VerifyInnerExceptionDerivedTypeTest3()
        {
            throw new MyExceptedException(null, new MyExtendedDummyException(null));
        }

        /// <summary>
        /// Tests VerifyInnerExceptionDerivedType method.
        /// </summary>
        [TestMethod]
        public void VerifyInnerExceptionDerivedTypeTest4()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { ExpectedInnerException = typeof(MyDummyException), AllowInnerExceptionDerivedType = true };
            Exception exception = new MyExceptedException("Warning!!!");

            try
            {
                Verify(target, exception);
                Assert.Fail("Verify failed.");
            }
            catch (AssertFailedException ex)
            {
                Assert.AreEqual("Test method UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest.VerifyInnerExceptionDerivedTypeTest4 threw exception UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyExceptedException without inner exception, but inner exception UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyDummyException was expected. Exception message: UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest+MyExceptedException: Warning!!!.", ex.Message);
            }
        }

        #endregion

        #region VerifyExceptionMessage

        /// <summary>
        /// Tests VerifyExceptionMessage method.
        /// </summary>
        [TestMethod]
        public void VerifyExceptionMessageTest1()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { ExpectedMessage = "Woh!!!" };
            Exception exception = new MyExceptedException("Grrr!!! Go away!");

            try
            {
                Verify(target, exception);
                Assert.Fail("Verify failed.");
            }
            catch (AssertFailedException ex)
            {
                Assert.AreEqual("Test method UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest.VerifyExceptionMessageTest1 threw exception with message Grrr!!! Go away!, but exception message Woh!!! was expected.", ex.Message);
            }
        }

        /// <summary>
        /// Tests VerifyExceptionMessage method.
        /// </summary>
        [TestMethod]
        public void VerifyExceptionMessageTest2()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { ExpectedMessage = "Grrr!!! Go away!" };
            Exception exception = new MyExceptedException("Grrr!!! Go away!");

            Verify(target, exception);
        }

        /// <summary>
        /// Tests VerifyExceptionMessage method.
        /// </summary>
        [TestMethod]
        [ThrowsException(typeof(MyExceptedException), ExpectedMessage = "Grrr!!! Go away!")]
        public void VerifyExceptionMessageTest3()
        {
            throw new MyExceptedException("Grrr!!! Go away!");
        }

        /// <summary>
        /// Tests VerifyExceptionMessage method.
        /// </summary>
        [TestMethod]
        [ThrowsException(typeof(MyExceptedException), ExpectedMessage = null)]
        public void VerifyExceptionMessageTest4()
        {
            throw new MyExceptedException(null);
        }

        #endregion

        #region VerifyInnerExceptionMessage

        /// <summary>
        /// Tests VerifyInnerExceptionMessage method.
        /// </summary>
        [TestMethod]
        public void VerifyInnerExceptionMessageTest1()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { ExpectedInnerExceptionMessage = "Woh!!!" };
            Exception exception = new MyExceptedException(null, new MyDummyException("Grrr!!! Go away!"));

            try
            {
                Verify(target, exception);
                Assert.Fail("Verify failed.");
            }
            catch (AssertFailedException ex)
            {
                Assert.AreEqual("Test method UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest.VerifyInnerExceptionMessageTest1 threw exception with inner exception message Grrr!!! Go away!, but inner exception message Woh!!! was expected.", ex.Message);
            }
        }

        /// <summary>
        /// Tests VerifyInnerExceptionMessage method.
        /// </summary>
        [TestMethod]
        public void VerifyInnerExceptionMessageTest2()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { ExpectedInnerExceptionMessage = "Grrr!!! Go away!" };
            Exception exception = new MyExceptedException(null, new MyDummyException("Grrr!!! Go away!"));

            Verify(target, exception);
        }

        /// <summary>
        /// Tests VerifyInnerExceptionMessage method.
        /// </summary>
        [TestMethod]
        [ThrowsException(typeof(MyExceptedException), ExpectedInnerExceptionMessage = "Grrr!!! Go away!")]
        public void VerifyInnerExceptionMessageTest3()
        {
            throw new MyExceptedException(null, new MyDummyException("Grrr!!! Go away!"));
        }

        /// <summary>
        /// Tests VerifyInnerExceptionMessage method.
        /// </summary>
        [TestMethod]
        [ThrowsException(typeof(MyExceptedException), ExpectedInnerExceptionMessage = null)]
        public void VerifyInnerExceptionMessageTest4()
        {
            throw new MyExceptedException(null, new MyDummyException(null));
        }

        #endregion

        #region VerifyExceptionMessageAsRegex

        /// <summary>
        /// Tests VerifyExceptionMessageAsRegex method.
        /// </summary>
        [TestMethod]
        public void VerifyExceptionMessageAsRegexTest1()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { ExpectedMessageAsRegex = @"(\d{1,3}\.){3}\d{1,3}" };
            Exception exception = new MyExceptedException("IP address");

            try
            {
                Verify(target, exception);
                Assert.Fail("Verify failed.");
            }
            catch (AssertFailedException ex)
            {
                Assert.AreEqual(@"Test method UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest.VerifyExceptionMessageAsRegexTest1 threw exception with message IP address, but it does not match the regular expression (\d{1,3}\.){3}\d{1,3}.", ex.Message);
            }
        }

        /// <summary>
        /// Tests VerifyExceptionMessageAsRegex method.
        /// </summary>
        [TestMethod]
        public void VerifyExceptionMessageAsRegexTest2()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { ExpectedMessageAsRegex = @"(\d{1,3}\.){3}\d{1,3}" };
            Exception exception = new MyExceptedException("192.168.1.20");

            Verify(target, exception);
        }

        /// <summary>
        /// Tests VerifyExceptionMessageAsRegex method.
        /// </summary>
        [TestMethod]
        [ThrowsException(typeof(MyExceptedException), ExpectedMessageAsRegex = "(\\d{1,3}\\.){3}\\d{1,3}")]
        public void VerifyExceptionMessageAsRegexTest3()
        {
            throw new MyExceptedException("192.168.1.20");
        }

        #endregion

        #region VerifyInnerExceptionMessageAsRegex

        /// <summary>
        /// Tests VerifyInnerExceptionMessageAsRegex method.
        /// </summary>
        [TestMethod]
        public void VerifyInnerExceptionMessageAsRegexTest1()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { ExpectedInnerExceptionMessageAsRegex = @"(\d{1,3}\.){3}\d{1,3}" };
            Exception exception = new MyExceptedException(null, new MyDummyException("IP address"));

            try
            {
                Verify(target, exception);
                Assert.Fail("Verify failed.");
            }
            catch (AssertFailedException ex)
            {
                Assert.AreEqual(@"Test method UnitTests.Ooaavee.TestTools.ThrowsExceptionAttributeTest.VerifyInnerExceptionMessageAsRegexTest1 threw exception with inner exception message IP address, but it does not match the regular expression (\d{1,3}\.){3}\d{1,3}.", ex.Message);
            }
        }

        /// <summary>
        /// Tests VerifyInnerExceptionMessageAsRegex method.
        /// </summary>
        [TestMethod]
        public void VerifyInnerExceptionMessageAsRegexTest2()
        {
            ThrowsExceptionAttribute target = new ThrowsExceptionAttribute(typeof(MyExceptedException)) { ExpectedInnerExceptionMessageAsRegex = @"(\d{1,3}\.){3}\d{1,3}" };
            Exception exception = new MyExceptedException(null, new MyDummyException("192.168.1.20"));

            Verify(target, exception);
        }

        /// <summary>
        /// Tests VerifyInnerExceptionMessageAsRegex method.
        /// </summary>
        [TestMethod]
        [ThrowsException(typeof(MyExceptedException), ExpectedInnerExceptionMessageAsRegex = "(\\d{1,3}\\.){3}\\d{1,3}")]
        public void VerifyInnerExceptionMessageAsRegexTest3()
        {
            throw new MyExceptedException(null, new MyDummyException("192.168.1.20"));
        }

        #endregion

        #region Helpers

        /// <summary>
        /// TextContext
        /// </summary>
        public TestContext TestContext { set; get; }

        /// <summary>
        /// Executes the Verify method.
        /// </summary>
        /// <param name="target">Test target.</param>
        /// <param name="exception">The exception that is thrown by the unit test.</param>
        private void Verify(ThrowsExceptionAttribute target, Exception exception)
        {
            PropertyInfo testContext = typeof(ExpectedExceptionBaseAttribute).GetProperty("TestContext", BindingFlags.NonPublic | BindingFlags.Instance);
            testContext.SetValue(target, TestContext, null);

            ThrowsExceptionAttribute_Accessor accessor = new ThrowsExceptionAttribute_Accessor(new PrivateObject(target));
            accessor.Verify(exception);
        }

        private class MyExceptedException : Exception
        {
            private readonly string _message;

            public MyExceptedException()
            {
            }

            public MyExceptedException(string message) : base(message)
            {
                _message = message;
            }

            public MyExceptedException(string message, Exception innerException) : base(message, innerException)
            {
                _message = message;
            }

            public override string Message
            {
                get { return _message; }
            }

        }

        private class MyExtendedException : MyExceptedException
        {
        }

        private class MyDummyException : Exception
        {
            private readonly string _message;

            public MyDummyException(string message) : base(message)
            {
                _message = message;
            }

            public override string Message
            {
                get { return _message; }
            }
        }

        private class MyExtendedDummyException : MyDummyException
        {
            public MyExtendedDummyException(string message) : base(message)
            {
            }
        }

        #endregion

    }
}
